/**
 * Copyright (c) 2024-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


const success: int = 0;
const fail: int = 1;

function main(): int {
    let failures: number = 0;

    failures += check(testBytePerElement(),"test BytePerElement for {{.item.objectType}}");
    failures += check(testTypedArrayIterator(),"test {{.item.objectType}} Iterator");
    failures += check(testTypedArrayIteratorEmptyArray(), "test {{.item.objectType}} Iterator Empty Array");
    failures += check(testTypedArrayIteratorSingleElement(), "test {{.item.objectType}} Iterator Single Element");
    failures += check(testTypedArrayIteratorAfterModification(), "test {{.item.objectType}} Iterator After Modification");
    failures += check(testTypedArrayIteratorAfterCompletion(), "test {{.item.objectType}} Iterator After Completion");

    failures += check(testEmptyTypedArrayToString(), "Try to Empty Array toString");
    failures += check(testNonEmptyTypedArrayToString(), "Try to Nonempty Array toString");

    failures += check(testEmptyTypedArrayAt(), "Test to Empty Array at");
    failures += check(testNonEmptyTypedArrayAt(), "Test to Nonempty Array at");
    
    failures += check(testNonEmptyTypedArrayReverse(), "Test NonEmpty Array Reverse");
    failures += check(testEmptyTypedArrayReverse(), "Test Empty Array Reverse");

    failures += check(testTypedArrayEvery(), "Test .every method")
    failures += check(testTypedArraySome(), "Test .some method")
    failures += check(testTypedArraySort(), "Test .sort method")
    {%- if (item.objectType == 'Float32Array') or (item.objectType == 'Float64Array') %}
    failures += check(testTypedArraySortWithNaN(), "Test .sort with NaN method")
    failures += check(testTypedArraySortWithNaNReversed(), "Test .sort in reversed order with NaN method")
    {%- endif%}
    failures += check(testTypedArrayFindAndFindIndex(), "Test .find and .findIndex method")
    failures += check(testTypedArrayMap(), "Test .map method")
    failures += check(testTypedArrayOf(), "Test .of method")
    failures += check(testTypedArrayIndexOf(), "Test .indexOf method")
    failures += check(testTypedArrayReduce(), "Test reduce method")
    failures += check(testTypedArrayReduceRight(), "Test reduceRight method")

    failures += check(testTypedArrayFill1Arg(), "Test fill(value) method")
    failures += check(testTypedArrayFill2Args(), "Test fill(value, start) method")
    failures += check(testTypedArrayFill3Args(), "Test fill(value, start, end) method")

    failures += check(testTypedArrayFind(), "Test .find method")
    failures += check(testTypedArrayFindIndex(), "Test .findIndex method")
    failures += check(testTypedArrayIndexOf2(), "Test .indexOf method v2")
    failures += check(testTypedArrayLastIndexOf(), "Test .lastIndexOf method")

    {% for i in range(5) -%}
    failures += check(testEntries({{.i}}), "Test .entries method");
    {%+ endfor %}

    if (failures > 0){
        console.log("failed");
        return fail;
    }

    console.log("All passed");
    return success;
}

function check(result: number, message: String): number {
    if (result == 0) {
        return success;
    }
    console.log("\nFAILED: " + message);
    return fail;
}

const source: FixedArray<{{.item.primitiveType}}> = {{.item.data}};
const abnormalSource: FixedArray<{{.item.primitiveType}}> = {{.item.abnormalData}};

function testBytePerElement(): number {
    if ({{.item.objectType}}.BYTES_PER_ELEMENT == {{.item.primitiveSizeBytes}}) return success;
    return fail;
}

function testTypedArrayIterator(): number {
    let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});
    let typedArray: {{.item.objectType}};

    try {
        typedArray = new {{.item.objectType}}(ss);
    } catch(e) {
        console.log(e);
        return fail;
    }

    let length = typedArray.length;

    try {
        typedArray[0] = {{.item.create}}(1);
        typedArray[1] = {{.item.create}}(2);
        typedArray[2] = {{.item.create}}(3);
        typedArray[3] = {{.item.create}}(4);
        typedArray[4] = {{.item.create}}(5);
    } catch(e) {
        console.log(e);
        return fail;
    }

    let arrayIterator = typedArray.$_iterator();
    let next = arrayIterator.next();
    let counter: int = 0;
    while (!next.done) {
        if (next.value == undefined) {
            return fail;
        }
        if (typedArray[counter]{{.item.cast2primitive}} != (next.value!){{.item.cast2primitive}}) {    
            return fail;
        }
        next = arrayIterator.next()
        counter++
    }

    if (counter != typedArray.length) { 
        return fail;
    }

    return success;
}



function testEmptyTypedArrayToString(): number {
    let ss = new ArrayBuffer(0); 
    let typedArray: {{.item.objectType}};
    try {
        typedArray = new {{.item.objectType}}(ss);
    } catch(e) {
        console.log(e);
        return fail;
    }

    const toStringResult = typedArray.toString();
    console.log("toString result: " + toStringResult);
    // Assertion
    if (toStringResult == "") {
        console.log("testEmptyTypedArrayToString test passed.");
        return success;
    } else {
        console.log("toString test failed.");
        return fail;
    }
}

function testNonEmptyTypedArrayToString(): number {
    let source1: FixedArray<{{.item.elementType}}> = [
        {{.item.create}}(1),
        {{.item.create}}(2),
        {{.item.create}}(3),
        {{.item.create}}(4),
        {{.item.create}}(5)];
    let ss = new ArrayBuffer(source1.length * {{.item.primitiveSizeBytes}});
    let typedArray: {{.item.objectType}};

    try {
        typedArray = new {{.item.objectType}}(ss);
        typedArray.set(source1);
    } catch(e) {
        console.log(e);
        return fail;
    }

    const toStringResult = typedArray.toString();
    console.log("toString result: " + toStringResult);
    // Assertion
    if (toStringResult == "1,2,3,4,5") {
        console.log("toString test passed.");
        return success;
    } else {
        console.log("testNonEmptyTypedArrayToString test failed.");
        return fail;
    }
}

function testNonEmptyTypedArrayAt(): number {
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});
    let typedArray: {{.item.objectType}};

    try {
        typedArray = new {{.item.objectType}}(ss);
        typedArray.set(source);
    } catch(e) {
        console.log(e);
        return fail;
    }

    let index = 0;
    let length = typedArray.length;
    let atResult = typedArray.at(index);
    console.log("testNonEmptyTypedArrayAt result [1/3]: " + atResult);

    // Assertion
    if (atResult == {{.item.create}}(source[index])) {
        console.log("testNonEmptyTypedArrayAt [1/3] test passed.");
    } else {
        console.log("testNonEmptyTypedArrayAt [1/3] test failed.");
        return fail;
    }

    index = -1;
    atResult = typedArray.at(index);
    console.log("testNonEmptyTypedArrayAt [2/3] result: " + atResult);
    // Assertion
    if (atResult == {{.item.create}}(source[index + length])) {
        console.log("testNonEmptyTypedArrayAt [2/3] test passed.");
    } else {
        console.log("testNonEmptyTypedArrayAt [2/3] test failed.");
        return fail;
    }

    index = typedArray.length;
    atResult = typedArray.at(index);
    console.log("testNonEmptyTypedArrayAt result [3/3]: " + atResult);
    // Assertion
    if (atResult == undefined) {
        console.log("testNonEmptyTypedArrayAt [3/3] test passed.");
    } else {
        console.log("testNonEmptyTypedArrayAt [3/3] test failed.");
        return fail;
    }
    return success;
}

function testEmptyTypedArrayAt(): number {
    let ss = new ArrayBuffer(0); 
    let typedArray: {{.item.objectType}};
    try {
        typedArray = new {{.item.objectType}}(ss);
    } catch(e) {
        console.log(e);
        return fail;
    }

    let atResult = typedArray.at(0);
    console.log("testEmptyTypedArrayAt result: " + atResult);
    // Assertion
    if (atResult == undefined) {
        console.log("testEmptyTypedArrayAt test passed.");
        return success;
    } else {
        console.log("testEmptyTypedArrayAt test failed.");
        return fail;
    }
}

function testNonEmptyTypedArrayReverse(): number {
    let source1: FixedArray<{{.item.elementType}}> = [
        {{.item.create}}(5),
        {{.item.create}}(3),
        {{.item.create}}(1),
        {{.item.create}}(4),
        {{.item.create}}(2)];
    let ss = new ArrayBuffer(source1.length * {{.item.primitiveSizeBytes}});
    let typedArray: {{.item.objectType}};

    try {
        typedArray = new {{.item.objectType}}(ss);
        typedArray.set(source1);
    } catch(e) {
        console.log(e);
        return fail;
    }

    // Test toReversed function
    let reversedArray: {{.item.objectType}} = typedArray.reverse();
    let expectedArray: FixedArray<{{.item.elementType}}> = [
        {{.item.create}}(2),
        {{.item.create}}(4),
        {{.item.create}}(1),
        {{.item.create}}(3),
        {{.item.create}}(5)];
    for (let i = 0; i < reversedArray.length; i++) {
        if (reversedArray[i] != expectedArray[i]) {
            console.log("Test failed. testNonEmptyTypedArrayReverse: " + JSON.stringify(reversedArray[i]));
            return fail;
        }
    }
    return success;
}

function testEmptyTypedArrayReverse(): number {
    let ss = new ArrayBuffer(0);
    let typedArray: {{.item.objectType}};

    try {
        typedArray = new {{.item.objectType}}(ss);
    } catch(e) {
        console.log(e);
        return fail;
    }

    // Test toReversed function
    let reversedArray: {{.item.objectType}} = typedArray.reverse();

    if (reversedArray != typedArray || reversedArray.length != 0) {
        console.log("Test failed. testEmptyTypedArrayReverse: " + reversedArray);
        return fail;
    }

    return success;
}

function testTypedArrayIteratorEmptyArray(): number {
    let emptyArray = new {{.item.objectType}}(new ArrayBuffer(0));
    let emptyIterator = emptyArray.$_iterator();
    let emptyNext = emptyIterator.next();

    if (!emptyNext.done || emptyNext.value != undefined) {
        return fail;
    }

    return success;
}

function testTypedArrayIteratorSingleElement(): number {
    let singleArray = new {{.item.objectType}}(new ArrayBuffer(1 *  {{.item.primitiveSizeBytes}}));

    try {
        singleArray[0] = {{.item.create}}(99);
    } catch(e) {
        console.log(e);
        return fail;
    }

    let singleIterator = singleArray.$_iterator();
    let singleNext = singleIterator.next();

    if (singleNext.done || singleNext.value != {{.item.create}}(99)) {
        return fail;
    }

    singleNext = singleIterator.next();
    if (!singleNext.done || singleNext.value != undefined) {
        return fail;
    }

    return success;
}

function testTypedArrayIteratorAfterModification(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});
  let typedArray: {{.item.objectType}};

  try {
      typedArray = new {{.item.objectType}}(ss);
  } catch(e) {
      console.log(e);
      return fail;
  }

  try {
      typedArray[0] = {{.item.create}}(1);
      typedArray[1] = {{.item.create}}(2);
      typedArray[2] = {{.item.create}}(3);
      typedArray[3] = {{.item.create}}(4);
      typedArray[4] = {{.item.create}}(5);
  } catch(e) {
      console.log(e);
      return fail;
  }

    // Testing iterator after modification
    let arrayIterator = typedArray.$_iterator();
    let next = arrayIterator.next();
    let expectedValues: FixedArray<{{.item.elementType}}> = [
        {{.item.create}}(1),
        {{.item.create}}(2),
        {{.item.create}}(3),
        {{.item.create}}(4),
        {{.item.create}}(5)];
    let counter: int = 0;

    while (!next.done) {
      if (next.value == undefined) {
          return fail;
      }
      if (expectedValues[counter] != next.value) {
          return fail;
      }
      next = arrayIterator.next();
      counter++;
    }

    if (counter != typedArray.length) {
        return fail;
    }

    // Modify a value
    typedArray[0] = {{.item.create}}(99);
    if (typedArray.$_iterator().next().value != {{.item.create}}(99)) {
        return fail;
    }
    return success;
}

function testTypedArrayIteratorAfterCompletion(): number {
    let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});
    let typedArray: {{.item.objectType}};

    try {
        typedArray = new {{.item.objectType}}(ss);
    } catch(e) {
        console.log(e);
        return fail;
    }

    typedArray[0] = {{.item.create}}(1);
    typedArray[1] = {{.item.create}}(2);
    typedArray[2] = {{.item.create}}(3);
    typedArray[3] = {{.item.create}}(4);
    typedArray[4] = {{.item.create}}(5);

    let arrayIterator = typedArray.$_iterator();
    let next = arrayIterator.next();

    while (!next.done) {
        next = arrayIterator.next();
    }

    // Check iterator after completion
    next = arrayIterator.next();
    if (!next.done || next.value != undefined) {
        return fail;
    }

    return success;
}

type ObjectTypeAndExpectations<T> = [{{.item.objectType}}, T]

const testTypedArrayEvery = parametrize<ObjectTypeAndExpectations<boolean[]>>(
    "testTypedArrayEvery<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of({{.item.create}}(1), {{.item.create}}(2), {{.item.create}}(3)),
            [true, false, true, false, true, false, false, true, false, true]
        ] as ObjectTypeAndExpectations<boolean[]>,
    ],
    (args: ObjectTypeAndExpectations<boolean[]>): number => {
        const array = new {{.item.objectType}}(args[0])
        const expected = args[1]

        let failures = 0

        const isTrueEverywhere = array.every(() => true)
        failures += check(boolToResult(isTrueEverywhere == expected[0]), `[${array}].every(() => true)`)

        const isFalseEverywhere = array.every(() => false)
        failures += check(boolToResult(isFalseEverywhere == expected[1]), `[${array}].every(() => false)`)

        const isPositive = array.every((x: {{.item.type}}) => x > {{.item.create}}(0))
        failures += check(boolToResult(isPositive == expected[2]), `[${array}].every(x > 0)`)

        const isNegative = array.every((x: {{.item.type}}) => x < {{.item.create}}(0))
        failures += check(boolToResult(isNegative == expected[3]), `[${array}].every(x < 0)`)

        const isValueEqualToIndexPlus1 = array.every(
            (x: {{.item.type}}, index: int) => x == {{.item.create}}(index + 1)
        )
        failures += check(
            boolToResult(isValueEqualToIndexPlus1 == expected[4]),
            `[${array}].every((x, index) => x == index + 1)`
        )

        const isValueEqualToIndex = array.every(
            (x: {{.item.type}}, index: int) => x == {{.item.create}}(index)
        )
        failures += check(
            boolToResult(isValueEqualToIndex == expected[5]),
            `[${array}].every((x, index) => x == index)`
        )

        const rhs = array.reduce((acc: {{.item.type}}, x: {{.item.type}}) => acc + x)
        const isArraySumEqualToIndexPlusValue = array.every(
            (x: {{.item.type}}, index: int, array: {{.item.objectType}}) => {
                return x + {{.item.create}}(index) == rhs
            }
        )
        failures += check(
            boolToResult(isArraySumEqualToIndexPlusValue == expected[6]),
            `[${array}].every((x, index, arr) => x + index == sum(arr))`
        )

        const eqByInd = (x: {{.item.type}}, index: int, arr: {{.item.objectType}}) => x == arr[index]
        const isValueEqualToArrayByIndex = array.every(eqByInd)
        failures += check(
            boolToResult(isValueEqualToArrayByIndex == expected[7]),
            `[${array}].every((x, index, arr) => x == arr[index])`
        )
        
        const isNoEvenNumbers = array.every(
            (x: {{.item.type}}, index: int, arr: {{.item.objectType}}) => {
                if (x{{.item.cast2primitive}} % 2 == 0) {
                    arr[index] = {{.item.create}}(0)
                    return false
                }
                return true
            }
        )
        failures += check(
            boolToResult(isNoEvenNumbers == expected[8]),
            `[${array}].every((x, index, arr) => *check and remove even numbers*)`
        )

        const isNoMoreEvenNumbers = array.every(
            (x: {{.item.type}}) => x{{.item.cast2primitive}} == 0 || x{{.item.cast2primitive}} % 2 != 0
        )
        failures += check(
            boolToResult(isNoMoreEvenNumbers == expected[9]),
            `[${array}].every((x) => x != 0 && x % 2 != 0)`
        )

        return failures == 0 ? success: fail
    }
)


const testTypedArraySome = parametrize<ObjectTypeAndExpectations<boolean[]>>(
    "testTypedArraySome<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of({{.item.create}}(1), {{.item.create}}(2)),
            [true, true, false, false]
        ] as ObjectTypeAndExpectations<boolean[]>,
        [
            {{.item.objectType}}.of({{.item.create}}(-1), {{.item.create}}(-2)),
            [true, false, false, false]
        ] as ObjectTypeAndExpectations<boolean[]>,
    ],
    (args: ObjectTypeAndExpectations<boolean[]>): number => {
        const array = args[0]
        const expected = args[1]

        const constTrue = array.some(() => true)
        const hasPositivesSome = array.some((x: {{.item.type}}) => x > {{.item.create}}(0))
        const hasElementsWithValueEqIndex = array.some((x: {{.item.type}}, index: int) => x == {{.item.create}}(index))

        const neByInd = (x: {{.item.type}}, index: int, arr: {{.item.objectType}}) => x != arr[index]
        const isValueNotEqualToArrayByIndex = array.some(neByInd)

        let failures = 0

        failures += check(boolToResult(constTrue == expected[0]), "const true")
        failures += check(boolToResult(hasPositivesSome == expected[1]), "has positives")
        failures += check(boolToResult(hasElementsWithValueEqIndex == expected[2]), "has elements where value == index")
        failures += check(boolToResult(isValueNotEqualToArrayByIndex == expected[3]), ".some(x, index, arr) => x != arr[index]")

        return failures == 0 ? success: fail
    }
)

const testTypedArraySort = parametrize<ObjectTypeAndExpectations<number[]>>(
    "testTypedArraySort",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(-1),
                {{.item.create}}(-100),
                {{.item.create}}(0),
                {{.item.create}}(111),
                {{.item.create}}(-50)
            ),
            [-100, -50, -1, 0, 111] as number[]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(-1),
                {{.item.create}}(-1),
                {{.item.create}}(0),
                {{.item.create}}(-1),
                {{.item.create}}(2)
            ),
            [-1, -1, -1, 0, 2] as number[]
        ] as ObjectTypeAndExpectations<number[]>,
    ],
    (args: ObjectTypeAndExpectations<number[]>): number => {
        const array = new {{.item.objectType}}(args[0])
        const expected = new {{.item.objectType}}(args[1])

        array.sort()

        const isEqualToGold =
            array.every((x: {{.item.type}}, index: int) => x == expected[index])

        // descending
        array.sort(
            (x: {{.item.type}}, y: {{.item.type}}) => {
                return new Number(y - x)
            }
        )

        const isEqualToGoldReversed =
            array.every((x: {{.item.type}}, index: int) => x == expected[expected.length - index - 1])

        let failures = 0

        failures += check(boolToResult(isEqualToGold), "is equal to gold result")
        failures += check(boolToResult(isEqualToGoldReversed), "is equal to gold reversed result")

        return failures == 0 ? success: fail
    }
)

{%- if (item.objectType == 'Float32Array') or (item.objectType == 'Float64Array') %}

function isEqualOrNaN(x: {{.item.type}}, y: {{.item.type}}) {
    return ((x == y) || (isNaN(x) && isNaN(y)))
}

const testTypedArraySortWithNaN = parametrize<ObjectTypeAndExpectations<number[]>>(
    "testTypedArraySortWithNaN",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(-1),
                {{.item.create}}(-100),
                {{.item.create}}(NaN),
                {{.item.create}}(0),
                {{.item.create}}(111),
                {{.item.create}}(-50)
            ),
            [-100, -50, -1, 0, 111, NaN] as number[]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(NaN),
                {{.item.create}}(-1),
                {{.item.create}}(-1),
                {{.item.create}}(0),
                {{.item.create}}(-1),
                {{.item.create}}(2)
            ),
            [-1, -1, -1, 0, 2, NaN] as number[]
        ] as ObjectTypeAndExpectations<number[]>,
    ],
    (args: ObjectTypeAndExpectations<number[]>): number => {
        const array = new {{.item.objectType}}(args[0])
        const expected = new {{.item.objectType}}(args[1])

        array.sort()

        const isEqualToGold =
            array.every((x: {{.item.type}}, index: int) => isEqualOrNaN(x, expected[index]))

        // asc
        array.sort((x: {{.item.type}}, y: {{.item.type}}) => {
                return new Number(x - y)
        })

        const isEqualToGoldAsc =
            array.every((x: {{.item.type}}, index: int) => isEqualOrNaN(x, expected[index]))


        let failures = 0
        failures += check(boolToResult(isEqualToGold), "is equal to gold result")
        failures += check(boolToResult(isEqualToGoldAsc), "is equal to gold asc result")
        return failures == 0 ? success: fail
    }
)

const testTypedArraySortWithNaNReversed = parametrize<ObjectTypeAndExpectations<number[]>>(
    "testTypedArraySortWithNaNReversed",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(-1),
                {{.item.create}}(-100),
                {{.item.create}}(NaN),
                {{.item.create}}(0),
                {{.item.create}}(111),
                {{.item.create}}(-50)
            ),
            [111, 0, -1, -50, -100, NaN] as number[]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(NaN),
                {{.item.create}}(-1),
                {{.item.create}}(-1),
                {{.item.create}}(0),
                {{.item.create}}(-1),
                {{.item.create}}(2)
            ),
            [2, 0, -1, -1, -1, NaN] as number[],
        ] as ObjectTypeAndExpectations<number[]>,
    ],
    (args: ObjectTypeAndExpectations<number[]>): number => {
        const array = new {{.item.objectType}}(args[0])
        const expected = new {{.item.objectType}}(args[1])

        // desc
        array.sort((x: {{.item.type}}, y: {{.item.type}}) => {
            return new Number(y - x)
        })

        const isEqualToGold =
            array.every((x: {{.item.type}}, index: int) => isEqualOrNaN(x, expected[index]))

        let failures = 0
        failures += check(boolToResult(isEqualToGold), "is equal to gold result")
        return failures == 0 ? success: fail
    }
)

{%- endif %}

type ApplyFunction<T> =
    (element: {{.item.type}}, index: number, array: {{.item.objectType}}) => T
    | (element: {{.item.type}}, index: number) => T
    | (element: {{.item.type}}) => T
    | () => T
type FindCallable = ApplyFunction<boolean>

type ExpectationsOptIN = [{{.item.type}} | undefined, number | undefined]
type FCAndExpectations = [FindCallable, ExpectationsOptIN]

const testTypedArrayFindAndFindIndex = parametrize<ObjectTypeAndExpectations<FCAndExpectations>>(
   "testTypedArrayFindAndFindIndex",
   [
       [
           {{.item.objectType}}.of(
               {{.item.create}}(1),
               {{.item.create}}(2),
               {{.item.create}}(-5),
               {{.item.create}}(111),
               {{.item.create}}(-50)
           ),
           [
               (element: {{.item.type}}, index: number, array: {{.item.objectType}}) => element < {{.item.create}}(0),
               [{{.item.create}}(-5), 2. as double] as ExpectationsOptIN
           ]
       ] as ObjectTypeAndExpectations<FCAndExpectations>,
   ],
   (args: ObjectTypeAndExpectations<FCAndExpectations>): number => {
       const array = new {{.item.objectType}}(args[0])
       const cb = args[1][0]
       const expected = args[1][1]

       let result: ExpectationsOptIN = [undefined, undefined]

       for (let i = 0; i < array.length; ++i) {
            let cbRes = cb(array[i], i, array)
            if (cbRes == true) {
                result = [array[i], i] as ExpectationsOptIN
                break
            }
       }
       const isEqual = result[0] == expected[0] && result[1] == expected[1]

       let failures = 0
       failures += check(boolToResult(isEqual), "is equal to [element, index]")
       return failures == 0 ? success: fail
   }
)


function testTypedArrayMap(): number {
    let ta = {{.item.objectType}}.of(
        {{.item.create}}(1),
        {{.item.create}}(2),
        {{.item.create}}(-5),
        {{.item.create}}(10),
        {{.item.create}}(-10)
    )

    let mapWithConst = ta.map((): {{.item.type}} => ta[0])
    const isMappedWithConst = mapWithConst.every((num: {{.item.type}}) => num == ta[0])

    let squared = ta.map((num: {{.item.type}}) => num * num)
    const isSquared = squared.every(
       (num: {{.item.type}}, index: int) => num == ta[index] * ta[index]
    )

    let mapWithIndex = ta.map((num: {{.item.type}}, index: int) => num + {{.item.create}}(index))
    const isMappedWithIndex = mapWithIndex.every(
       (num: {{.item.type}}, index: int) => num == ta[index] + {{.item.create}}(index)
    )

    let mapWithIndexAndThis = ta.map((num: {{.item.type}}, index: int, array: {{.item.objectType}}) => num)
    const isMappedWithIndexAndThis = mapWithIndexAndThis.every(
       (num: {{.item.type}}, index: int, array: {{.item.objectType}}) => num == array[index]
    )

    let failures = 0

    failures += check(boolToResult(isMappedWithConst), "is equal to constant element at 0 index")
    failures += check(boolToResult(isSquared), "is equal to newNum := num * num")
    failures += check(boolToResult(isMappedWithIndex), "is equal to newNum := num + index")
    failures += check(boolToResult(isMappedWithIndexAndThis), "is equal to newNum := num with full args list")

    return failures == 0 ? success: fail
}

const testTypedArrayOf = parametrize<FixedArray<{{.item.type}}>>(
   "testTypedArrayOf",
   [
       [
           {{.item.create}}(-1),
           {{.item.create}}(-2),
           {{.item.create}}(0),
           {{.item.create}}(3),
           {{.item.create}}(4)
       ] as FixedArray<{{.item.type}}>
   ],
   (args: FixedArray<{{.item.type}}>): number => {
       const viaCtor = new {{.item.objectType}}(args)
       const viaOf = {{.item.objectType}}.of(...args)

       const isEqual = viaCtor.every((x: {{.item.type}}, index: int) => x == viaOf[index])

       let failures = 0
       failures += check(boolToResult(isEqual), "result of .of method call is equal to ctor invoke")
       failures += check(boolToResult(viaCtor.length == viaOf.length), "length of results (using constructor and .of method) must be equal")
       return failures == 0 ? success: fail
   }
)

const testTypedArrayIndexOf = parametrize<ObjectTypeAndExpectations<[{{.item.type}}, number]>>(
    "testTypedArrayIndexOf",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(-1),
                {{.item.create}}(-2),
                {{.item.create}}(0),
                {{.item.create}}(3),
                {{.item.create}}(4)
            ),
            [
                {{.item.create}}(3), // value to search for
                3 // expected index
            ]
        ] as ObjectTypeAndExpectations<[{{.item.type}}, number]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(10),
                {{.item.create}}(20),
                {{.item.create}}(30),
                {{.item.create}}(40)
            ),
            [
                {{.item.create}}(20), // value to search for
                1 // expected index
            ]
        ] as ObjectTypeAndExpectations<[{{.item.type}}, number]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(2),
                {{.item.create}}(3)
            ),
            [
                {{.item.create}}(4), // value to search for (not found)
                -1 // expected result when not found
            ]
        ] as ObjectTypeAndExpectations<[{{.item.type}}, number]>,
    ],
    (args: ObjectTypeAndExpectations<[{{.item.type}}, number]>): number => {
        const array = new {{.item.objectType}}(args[0])
        const valueToSearch = args[1][0]
        const expectedIndex = args[1][1]

        const resultIndex = array.indexOf(valueToSearch)

        const isCorrectIndex = resultIndex == expectedIndex

        let failures = 0
        failures += check(boolToResult(isCorrectIndex), `expected index ${expectedIndex}, got ${resultIndex}`)
        return failures == 0 ? success : fail
    }
)

const testTypedArrayReduce = parametrize<ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>>(
    "testTypedArrayReduce",
    [
        // Test case: Using reduce with an initial value and a simple addition callback
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(2),
                {{.item.create}}(3),
                {{.item.create}}(4)
            ),
            [
                {{.item.create}}(10), // Initial value for reduction
                {{.item.create}}(20)  // Expected result after reduction (10 + 1 + 2 + 3 + 4)
            ]
        ] as ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>,

        // Test case: Using reduce without an initial value and a simple addition callback
        [
            {{.item.objectType}}.of(
                {{.item.create}}(5),
                {{.item.create}}(10),
                {{.item.create}}(15)
            ),
            [
                {{.item.create}}(0), // Initial value for reduction
                {{.item.create}}(30), // Expected result (5 + 10 + 15)
            ]
        ] as ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>,
    ],
    (args: ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>): number => {
        const array = new {{.item.objectType}}(args[0])
        const expectedResult = args[1][1]
        const initialValue = args[1][0]

        // Test callback signatures:

        // () => {{.item.type}} (No parameters, just returns a constant)
        const testNoParams = array.reduce((): {{.item.type}} => {{.item.create}}(100), initialValue)
        const isCorrectNoParams = testNoParams == {{.item.create}}(100)

        // (prev: {{.item.type}}) => {{.item.type}} (Using only the previous value, no current element)
        const testPrevOnly = array.reduce((prev: {{.item.type}}): {{.item.type}} => prev + {{.item.create}}(1), initialValue)
        const isCorrectPrevOnly = testPrevOnly == initialValue + {{.item.create}}(array.length)

        // (prev: {{.item.type}}, cur: {{.item.type}}) => {{.item.type}} (Using previous value and current element)
        const testPrevCur = array.reduce((prev: {{.item.type}}, cur: {{.item.type}}): {{.item.type}} => prev + cur, initialValue)
        const isCorrectPrevCur = testPrevCur == expectedResult

        // (prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int) => {{.item.type}} (Using prev, current element, and index)
        const testPrevCurIndex = array.reduce((prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int): {{.item.type}} => prev + cur + {{.item.create}}(curIndex), initialValue)
        const expectedWithIndices = array.reduce((sum: {{.item.type}}, val: {{.item.type}}, idx: int) => sum + val + {{.item.create}}(idx), initialValue)
        const isCorrectPrevCurIndex = testPrevCurIndex == {{.item.create}}(expectedWithIndices)

        // (prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int, array: {{.item.objectType}}) => {{.item.type}} (Using prev, current element, index, and array)
        const testPrevCurIndexArray = array.reduce((prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int, arr: {{.item.objectType}}): {{.item.type}} => prev + cur + arr[curIndex], initialValue)
        const expectedResultFull = array.reduce((sum: {{.item.type}}, val: {{.item.type}}, idx: int, arr: {{.item.objectType}}) => sum + val + arr[idx], initialValue)
        const isCorrectPrevCurIndexArray = testPrevCurIndexArray == {{.item.create}}(expectedResultFull)

        // Validating all results
        let failures = 0
        failures += check(boolToResult(isCorrectNoParams), `Expected constant result 100, got ${testNoParams} on noParams`)
        failures += check(boolToResult(isCorrectPrevOnly), `Expected ${expectedResult + {{.item.create}}(array.length)}, got ${testPrevOnly} on prevOnly`)
        failures += check(boolToResult(isCorrectPrevCur), `Expected ${expectedResult}, got ${testPrevCur} on prevCur`)
        failures += check(boolToResult(isCorrectPrevCurIndex), `Expected ${expectedWithIndices}, got ${testPrevCurIndex} on prevCurIndex`)
        failures += check(boolToResult(isCorrectPrevCurIndexArray), `Expected ${expectedResultFull}, got ${testPrevCurIndexArray} on prevCurIndexArray`)

        return failures == 0 ? success : fail
    }
)

const testTypedArrayReduceRight = parametrize<ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>>(
    "testTypedArrayReduceRight",
    [
        // Test case: Using reduceRight with an initial value and a simple addition callback
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(2),
                {{.item.create}}(3),
                {{.item.create}}(4)
            ),
            [
                {{.item.create}}(10), // Initial value for reduction
                {{.item.create}}(20)  // Expected result after reduction (10 + 4 + 3 + 2 + 1)
            ]
        ] as ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>,

        // Test case: Using reduceRight without an initial value and a simple addition callback
        [
            {{.item.objectType}}.of(
                {{.item.create}}(5),
                {{.item.create}}(10),
                {{.item.create}}(15)
            ),
            [
                {{.item.create}}(0), // Initial value for reduction
                {{.item.create}}(30), // Expected result (15 + 10 + 5)
            ]
        ] as ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>,
    ],
    (args: ObjectTypeAndExpectations<[{{.item.type}}, {{.item.type}}]>): number => {
        const array = new {{.item.objectType}}(args[0])
        const expectedResult = args[1][1]
        const initialValue = args[1][0]

        // Test callback signatures:

        // () => {{.item.type}} (No parameters, just returns a constant)
        const testNoParams = array.reduceRight((): {{.item.type}} => {{.item.create}}(100), initialValue)
        const isCorrectNoParams = testNoParams == {{.item.create}}(100)

        // (prev: {{.item.type}}) => {{.item.type}} (Using only the previous value, no current element)
        const testPrevOnly = array.reduceRight((prev: {{.item.type}}): {{.item.type}} => prev + {{.item.create}}(1), initialValue)
        const isCorrectPrevOnly = testPrevOnly == initialValue + {{.item.create}}(array.length)

        // (prev: {{.item.type}}, cur: {{.item.type}}) => {{.item.type}} (Using previous value and current element)
        const testPrevCur = array.reduceRight((prev: {{.item.type}}, cur: {{.item.type}}): {{.item.type}} => prev + cur, initialValue)
        const isCorrectPrevCur = testPrevCur == expectedResult

        // (prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int) => {{.item.type}} (Using prev, current element, and index)
        const testPrevCurIndex = array.reduceRight((prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int): {{.item.type}} => prev + cur + {{.item.create}}(curIndex), initialValue)
        const expectedWithIndices = array.reduceRight((sum: {{.item.type}}, val: {{.item.type}}, idx: int) => sum + val + {{.item.create}}(idx), initialValue)
        const isCorrectPrevCurIndex = testPrevCurIndex == {{.item.create}}(expectedWithIndices)

        // (prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int, array: {{.item.objectType}}) => {{.item.type}} (Using prev, current element, index, and array)
        const testPrevCurIndexArray = array.reduceRight((prev: {{.item.type}}, cur: {{.item.type}}, curIndex: int, arr: {{.item.objectType}}): {{.item.type}} => prev + cur + arr[curIndex], initialValue)
        const expectedResultFull = array.reduceRight((sum: {{.item.type}}, val: {{.item.type}}, idx: int, arr: {{.item.objectType}}) => sum + val + arr[idx], initialValue)
        const isCorrectPrevCurIndexArray = testPrevCurIndexArray == {{.item.create}}(expectedResultFull)

        // Validating all results
        let failures = 0
        failures += check(boolToResult(isCorrectNoParams), `Expected constant result 100, got ${testNoParams} on noParams`)
        failures += check(boolToResult(isCorrectPrevOnly), `Expected ${expectedResult + {{.item.create}}(array.length)}, got ${testPrevOnly} on prevOnly`)
        failures += check(boolToResult(isCorrectPrevCur), `Expected ${expectedResult}, got ${testPrevCur} on prevCur`)
        failures += check(boolToResult(isCorrectPrevCurIndex), `Expected ${expectedWithIndices}, got ${testPrevCurIndex} on prevCurIndex`)
        failures += check(boolToResult(isCorrectPrevCurIndexArray), `Expected ${expectedResultFull}, got ${testPrevCurIndexArray} on prevCurIndexArray`)

        return failures == 0 ? success : fail
    }
)

const testTypedArrayFill1Arg = parametrize<ObjectTypeAndExpectations<boolean[]>>(
    "testTypedArrayFill1Arg<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of({{.item.create}}(10), {{.item.create}}(20)),
            {%- if (item.objectType != 'Float32Array') and (item.objectType != 'Float64Array') %}
            [true, true, true, true]
            {%- else %}
            [true, false, false, false]
            {%- endif %}
        ] as ObjectTypeAndExpectations<boolean[]>,
    ],
    (args: ObjectTypeAndExpectations<boolean[]>): number => {
        const array = args[0]
        const expected = args[1]
        const sources = [source, abnormalSource]
        const copy = () => new {{.item.objectType}}(array)

        let failures = 0

        for (let src of sources) {
            for (let val of src) {
                const value = {{.item.create}}(val)
                const fillVal = copy().fill(value);
                const isAllChanged = fillVal.every((x) => x == value)
                failures += check(boolToResult(isAllChanged == expected[0]), `.fill(value) = ${fillVal}`)
            }
        }

        {%- if item.objectType != 'BigInt64Array' %}

        const fillNaN = copy().fill({{.item.create}}(NaN))
        const isNanAllZeroes = fillNaN.every((x) => x == {{.item.create}}(0))
        failures += check(boolToResult(isNanAllZeroes == expected[1]), `.fill(NaN) = ${fillNaN}`)

        const fillPosInf = copy().fill({{.item.create}}(+Infinity))
        const isPosInfAllZeroes = fillPosInf.every((x) => x == {{.item.create}}(0))
        failures += check(boolToResult(isPosInfAllZeroes == expected[2]), `.fill(+Infinity) = ${fillPosInf}`)

        const fillNegInf = copy().fill({{.item.create}}(-Infinity))
        const isNegInfAllZeroes = fillNegInf.every((x) => x == {{.item.create}}(0))
        failures += check(boolToResult(isNegInfAllZeroes == expected[3]), `.fill(-Infinity) = ${fillNegInf}`)

        {%- endif %}

        return failures == 0 ? success: fail
    }
)

const testTypedArrayFill2Args = parametrize<ObjectTypeAndExpectations<boolean[]>>(
    "testTypedArrayFill2Args<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(10),
                {{.item.create}}(20),
                {{.item.create}}(30),
                {{.item.create}}(40)
            ),
            [true, true, true, true, true]
        ] as ObjectTypeAndExpectations<boolean[]>,
    ],
    (args: ObjectTypeAndExpectations<boolean[]>): number => {
        const array = args[0]
        const expected = args[1]
        const sources = [source, abnormalSource]
        const last = array.length - 1
        const copy = () => new {{.item.objectType}}(array)

        let failures = 0

        for (let src of sources) {
            for (let val of src) {
                const value = {{.item.create}}(val)

                const fill0 = copy().fill(value, 0);
                const isAllChanged = fill0.every((x) => x == value)
                failures += check(boolToResult(isAllChanged == expected[0]), `.fill(value, 0) = ${fill0}`)
                
                const fill1 = copy().fill(value, 1)
                const isAllChangedExceptFirst = fill1[0] == array[0] && fill1.slice(1).every((x) => x == value)
                failures += check(boolToResult(isAllChangedExceptFirst == expected[1]), `.fill(value, 1) = ${fill1}`)
                
                const fillLen = copy().fill(value, array.length)
                const isNothingChanged = fillLen.every((x, i) => x == array[i])
                failures += check(boolToResult(isNothingChanged == expected[2]), `.fill(value, array.length) = ${fillLen}`)
                
                const fillM1 = copy().fill(value, -1)
                const isOnlyLastChanged = fillM1[last] == value && fillM1.slice(0, last - 1).every((x, i) => x == array[i])
                failures += check(boolToResult(isOnlyLastChanged == expected[3]), `.fill(value, -1) = ${fillM1}`)
                
                const fillMLen = copy().fill(value, -array.length);
                const isAllChangedNeg = fillMLen.every((x) => x == value)
                failures += check(boolToResult(isAllChangedNeg == expected[4]), `.fill(value, -array.length) = ${fillMLen}`)
            }
        }

        return failures == 0 ? success: fail
    }
)

const testTypedArrayFill3Args = parametrize<ObjectTypeAndExpectations<boolean[]>>(
    "testTypedArrayFill3Args<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(10),
                {{.item.create}}(20),
                {{.item.create}}(30),
                {{.item.create}}(40)
            ),
            [true, true, true, true, true, true, true, true, true]
        ] as ObjectTypeAndExpectations<boolean[]>,
    ],
    (args: ObjectTypeAndExpectations<boolean[]>): number => {
        const array = args[0]
        const expected = args[1]
        const sources = [source, abnormalSource]
        const last = array.length - 1
        const copy = () => new {{.item.objectType}}(array)

        let failures = 0

        for (let src of sources) {
            for (let val of src) {
                const value = {{.item.create}}(val)

                const fill00 = copy().fill(value, 0, 0)
                const isNothingChanged = fill00.every((x, i) => x == array[i])
                failures += check(boolToResult(isNothingChanged == expected[0]), `.fill(value, 0, 0) = ${fill00}`)
    
                const fill01 = copy().fill(value, 0, 1)
                const isOnlyFirstChanged = fill01[0] == value && fill01.slice(1).every((x, i) => x == array[i + 1])
                failures += check(boolToResult(isOnlyFirstChanged == expected[1]), `.fill(value, 0, 1) = ${fill01}`)
      
                const fill0Len = copy().fill(value, 0, array.length)
                const isAllChanged = fill0Len.every((x) => x == value)
                failures += check(boolToResult(isAllChanged == expected[2]), `.fill(value, 0, array.length) = ${fill0Len}`)
       
                const fill0M1 = copy().fill(value, 0, -1)
                const isAllChangedExceptLast = fill0M1[last] == array[last] && fill0M1.slice(0, last - 1).every((x) => x == value)
                failures += check(boolToResult(isAllChangedExceptLast == expected[3]), `.fill(value, 0, -1) = ${fill0M1}`)
          
                const fill0MLen = copy().fill(value, 0, -array.length)
                const isNothingChangedNeg = fill0MLen.every((x, i) => x == array[i])
                failures += check(boolToResult(isNothingChangedNeg == expected[4]), `.fill(value, 0, -array.length) = ${fill0MLen}`)

                const fill1Last = copy().fill(value, 1, last)
                const isOnlyCenterChanged = fill1Last[0] == array[0] && fill1Last[last] == array[last] &&
                    fill1Last.slice(1, last - 1).every((x) => x == value)
                failures += check(boolToResult(isOnlyCenterChanged == expected[5]), `.fill(value, 0, last) = ${fill1Last}`)

                const fill1M1 = copy().fill(value, 1, -1)
                const isOnlyCenterChangedNeg = fill1M1[0] == array[0] && fill1M1[last] == array[last] &&
                    fill1M1.slice(1, last - 1).every((x) => x == value)
                failures += check(boolToResult(isOnlyCenterChangedNeg == expected[6]), `.fill(value, 0, -1) = ${fill1M1}`)
      
                const fillLast1 = copy().fill(value, last, 1)
                const isNothingChangedReverse = fillLast1.every((x, i) => x == array[i])
                failures += check(boolToResult(isNothingChangedReverse == expected[7]), `.fill(value, last, 1) = ${fillLast1}`)

                const fillM11 = copy().fill(value, -1, 1)
                const isNothingChangedReverseNeg = fillM11.every((x, i) => x == array[i])
                failures += check(boolToResult(isNothingChangedReverseNeg == expected[8]), `.fill(value, -1, 1) = ${fillM11}`)
            }
        }

        return failures == 0 ? success: fail
    }
)


const testTypedArrayFind = parametrize<ObjectTypeAndExpectations<({{.item.type}}|undefined)[]>>(
    "testTypedArrayFind<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(2),
                {{.item.create}}(3),
                {{.item.create}}(4),
                {{.item.create}}(5)
            ),
            [
                {{.item.create}}(1),
                undefined,
                {{.item.create}}(4),
                undefined,
                {{.item.create}}(3),
                undefined,
                {{.item.create}}(2),
                undefined,
                {{.item.create}}(1),
                {{.item.create}}(5),
                {{.item.create}}(5),
            ]
        ] as ObjectTypeAndExpectations<({{.item.type}}|undefined)[]>,
        [
            new {{.item.objectType}}(),
            [
                undefined, undefined, undefined, undefined, undefined,
                undefined, undefined, undefined, undefined, undefined,
                undefined
            ]
        ] as ObjectTypeAndExpectations<({{.item.type}}|undefined)[]>,
    ],
    (args: ObjectTypeAndExpectations<({{.item.type}}|undefined)[]>): number => {
        const array = args[0]
        const expected = args[1]

        let failures = 0

        const alwaysTrue = array.find(() => true)
        failures += check(
            boolToResult(alwaysTrue == expected[0]),
            `[${array}].find(() => true) = ${alwaysTrue}`
        )

        const alwaysFalse = array.find(() => false)
        failures += check(
            boolToResult(alwaysFalse == expected[1]),
            `[${array}].find(() => false) = ${alwaysFalse}`
        )

        const valueMatches = array.find((value) => value > {{.item.create}}(3))
        failures += check(
            boolToResult(valueMatches == expected[2]),
            `[${array}].find((value) => value > 3) = ${valueMatches}`
        )

        const noValueMatches = array.find((value) => value > {{.item.create}}(10))
        failures += check(
            boolToResult(noValueMatches == expected[3]),
            `[${array}].find((value) => value > 10) = ${noValueMatches}`
        )
        
        const accessIndexArray = array.find(
            (value, index, obj) => value === {{.item.create}}(3) && index === 2 && obj === array
        )
        failures += check(
            boolToResult(accessIndexArray == expected[4]),
            `[${array}].find((value, index, obj) => value === 3 && index === 2 && obj === array) = ${accessIndexArray}`
        )

        const noIndexArrayMatch = array.find((value, index, obj) => value === {{.item.create}}(3) && index === 1 && obj === array)
        failures += check(
            boolToResult(noIndexArrayMatch == expected[5]),
            `[${array}].find((value, index, obj) => value === 3 && index === 1 && obj === array) = ${noIndexArrayMatch}`
        )

        const valueIndexMatch = array.find((value, index) => value === {{.item.create}}(2) && index === 1)
        failures += check(
            boolToResult(valueIndexMatch == expected[6]),
            `[${array}].find((value, index) => value === 2 && index === 1) = ${valueIndexMatch}`
        )

        const noValueIndexMatch = array.find((value, index) => value === {{.item.create}}(2) && index === 2)
        failures += check(
            boolToResult(noValueIndexMatch == expected[7]),
            `[${array}].find((value, index) => value === 2 && index === 2) = ${noValueIndexMatch}`
        )

        const matchesStart = array.find((value, index) => index == 0)
        failures += check(
            boolToResult(matchesStart == expected[8]),
            `[${array}].find((value, index) => index === 0) = ${matchesStart}`
        )

        const matchesEnd = array.find((value, index) => index == array.length - 1)
        failures += check(
            boolToResult(matchesEnd == expected[9]),
            `[${array}].find((value, index) => index === array.length - 1) = ${matchesEnd}`
        )

        const mutable = new {{.item.objectType}}(array)
        const changesArray = mutable.find((value, index, obj) => {
            if (index === 1) {
                obj[index] = {{.item.create}}(10)
            }
            return value > {{.item.create}}(4)
        })
        failures += check(
            boolToResult(changesArray == expected[10]),
            `[${mutable}].find((value, index, obj) => { /* changes array */}) = ${changesArray}`
        )

        return failures == 0 ? success: fail
    }
)

const testTypedArrayFindIndex = parametrize<ObjectTypeAndExpectations<number[]>>(
    "testTypedArrayFindIndex<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(2),
                {{.item.create}}(3),
                {{.item.create}}(4),
                {{.item.create}}(5)
            ),
            [
                0, -1, 3, -1, 2, -1, 1, -1, 0, 4, 4,
            ]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(3),
                {{.item.create}}(3),
                {{.item.create}}(3),
                {{.item.create}}(5)
            ),
            [
                0, -1, 4, -1, 2, 1, -1, -1, 0, 4, 4,
            ]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            new {{.item.objectType}}(),
            [
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
            ]
        ] as ObjectTypeAndExpectations<number[]>,
    ],
    (args: ObjectTypeAndExpectations<number[]>): number => {
        const array = args[0]
        const expected = args[1]

        let failures = 0

        const alwaysTrueIndex = array.findIndex(() => true)
        failures += check(
            boolToResult(alwaysTrueIndex == expected[0]),
            `[${array}].findIndex(() => true) = ${alwaysTrueIndex}`
        )

        const alwaysFalseIndex = array.findIndex(() => false)
        failures += check(
            boolToResult(alwaysFalseIndex == expected[1]),
            `[${array}].findIndex(() => false) = ${alwaysFalseIndex}`
        )

        const valueMatchesIndex = array.findIndex((value) => value > {{.item.create}}(3))
        failures += check(
            boolToResult(valueMatchesIndex == expected[2]),
            `[${array}].findIndex((value) => value > 3) = ${valueMatchesIndex}`
        )

        const noValueMatchesIndex = array.findIndex((value) => value > {{.item.create}}(10))
        failures += check(
            boolToResult(noValueMatchesIndex == expected[3]),
            `[${array}].findIndex((value) => value > 10) = ${noValueMatchesIndex}`
        )

        const accessIndexArrayIndex = array.findIndex(
            (value, index, obj) => value === {{.item.create}}(3) && index === 2 && obj === array
        )
        failures += check(
            boolToResult(accessIndexArrayIndex == expected[4]),
            `[${array}].findIndex((value, index, obj) => value === 3 && index === 2 && obj === array) = ${accessIndexArrayIndex}`
        )

        const noIndexArrayMatchIndex = array.findIndex(
            (value, index, obj) => value === {{.item.create}}(3) && index === 1 && obj === array
        )
        failures += check(
            boolToResult(noIndexArrayMatchIndex == expected[5]),
            `[${array}].findIndex((value, index, obj) => value === 3 && index === 1 && obj === array) = ${noIndexArrayMatchIndex}`
        )

        const valueIndexMatchIndex = array.findIndex((value, index) => value === {{.item.create}}(2) && index === 1)
        failures += check(
            boolToResult(valueIndexMatchIndex == expected[6]),
            `[${array}].findIndex((value, index) => value === 2 && index === 1) = ${valueIndexMatchIndex}`
        )

        const noValueIndexMatchIndex = array.findIndex((value, index) => value === {{.item.create}}(2) && index === 2)
        failures += check(
            boolToResult(noValueIndexMatchIndex == expected[7]),
            `[${array}].findIndex((value, index) => value === 2 && index === 2) = ${noValueIndexMatchIndex}`
        )

        const matchesStartIndex = array.findIndex((value, index) => index === 0)
        failures += check(
            boolToResult(matchesStartIndex == expected[8]),
            `[${array}].findIndex((value, index) => index === 0) = ${matchesStartIndex}`
        )

        const matchesEndIndex = array.findIndex((value, index) => index === array.length - 1)
        failures += check(
            boolToResult(matchesEndIndex == expected[9]),
            `[${array}].findIndex((value, index) => index === array.length - 1) = ${matchesEndIndex}`
        )

        const mutableIndexArray = new {{.item.objectType}}(array)
        const changesArrayIndex = mutableIndexArray.findIndex((value, index, obj) => {
            if (index === 1) {
                obj[index] = {{.item.create}}(10)
            }
            return value > {{.item.create}}(4)
        })
        failures += check(
            boolToResult(changesArrayIndex == expected[10]),
            `[${mutableIndexArray}].findIndex((value, index, obj) => { /* changes array */ }) = ${changesArrayIndex}`
        )

        return failures == 0 ? success: fail
    }
)

const testTypedArrayIndexOf2 = parametrize<ObjectTypeAndExpectations<number[]>>(
    "testTypedArrayIndexOf2<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(2),
                {{.item.create}}(3),
                {{.item.create}}(4),
                {{.item.create}}(5)
            ),
            [
                0, -1, 2, -1, 3, -1, 1, -1, 3, 4, -1, -1, 0, -1
            ]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(3),
                {{.item.create}}(3),
                {{.item.create}}(3),
                {{.item.create}}(5)
            ),
            [
                0, -1, 2, -1, -1, -1, -1, -1, -1, 4, -1, 3, 0, -1
            ]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            new {{.item.objectType}}(),
            [
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
            ]
        ] as ObjectTypeAndExpectations<number[]>,
    ],
    (args: ObjectTypeAndExpectations<number[]>): number => {
        const array = args[0]
        const expected = args[1]
        
        let failures = 0
        
        const indexOfFirstElement = array.indexOf({{.item.create}}(1))
        failures += check(
            boolToResult(indexOfFirstElement == expected[0]),
            `[${array}].indexOf(1) = ${indexOfFirstElement}`
        )
        
        const indexOfNonExistent = array.indexOf({{.item.create}}(42))
        failures += check(
            boolToResult(indexOfNonExistent == expected[1]),
            `[${array}].indexOf(42) = ${indexOfNonExistent}`
        )
        
        const indexOfWithStartIndex = array.indexOf({{.item.create}}(3), 2)
        failures += check(
            boolToResult(indexOfWithStartIndex == expected[2]),
            `[${array}].indexOf(3, 2) = ${indexOfWithStartIndex}`
        )
        
        const indexOfWithStartIndexOutOfBounds = array.indexOf({{.item.create}}(3), 10)
        failures += check(
            boolToResult(indexOfWithStartIndexOutOfBounds == expected[3]),
            `[${array}].indexOf(3, 10) = ${indexOfWithStartIndexOutOfBounds}`
        )
        
        const indexOfIntStartIndex = array.indexOf({{.item.create}}(4), 3 as int)
        failures += check(
            boolToResult(indexOfIntStartIndex == expected[4]),
            `[${array}].indexOf(4, 3 as int) = ${indexOfIntStartIndex}`
        )
        
        const indexOfIntOutOfBounds = array.indexOf({{.item.create}}(5), 5 as int)
        failures += check(
            boolToResult(indexOfIntOutOfBounds == expected[5]),
            `[${array}].indexOf(5, 5 as int) = ${indexOfIntOutOfBounds}`
        )
        
        const indexOfIntElement = array.indexOf(2 as int)
        failures += check(
            boolToResult(indexOfIntElement == expected[6]),
            `[${array}].indexOf(2 as int) = ${indexOfIntElement}`
        )
        
        const indexOfIntNonExistent = array.indexOf(99 as int)
        failures += check(
            boolToResult(indexOfIntNonExistent == expected[7]),
            `[${array}].indexOf(99 as int) = ${indexOfIntNonExistent}`
        )
        
        const indexOfIntWithStartIndex = array.indexOf(4 as int, 2)
        failures += check(
            boolToResult(indexOfIntWithStartIndex == expected[8]),
            `[${array}].indexOf(4 as int, 2) = ${indexOfIntWithStartIndex}`
        )
        
        const indexOfIntWithBothInt = array.indexOf(5 as int, 4 as int)
        failures += check(
            boolToResult(indexOfIntWithBothInt == expected[9]),
            `[${array}].indexOf(5 as int, 4 as int) = ${indexOfIntWithBothInt}`
        )

        const indexOfIntWithNegativeStart = array.indexOf(3 as int, -1 as int)
        failures += check(
            boolToResult(indexOfIntWithNegativeStart == expected[10]),
            `[${array}].indexOf(3 as int, -1 as int) = ${indexOfIntWithNegativeStart}`
        )

        const indexOfNegativeFromIndex = array.indexOf({{.item.create}}(3), -2)
        failures += check(
            boolToResult(indexOfNegativeFromIndex == expected[11]),
            `[${array}].indexOf(3, -2) = ${indexOfNegativeFromIndex}`
        )

        const indexOfNegativeStartOutOfBounds = array.indexOf({{.item.create}}(1), -10)
        failures += check(
            boolToResult(indexOfNegativeStartOutOfBounds == expected[12]),
            `[${array}].indexOf(1, -10) = ${indexOfNegativeStartOutOfBounds}`
        )
        
        const indexOfNonExistentWithValidStart = array.indexOf(99 as int, 2 as int)
        failures += check(
            boolToResult(indexOfNonExistentWithValidStart == expected[13]),
            `[${array}].indexOf(99 as int, 2 as int) = ${indexOfNonExistentWithValidStart}`
        )
        
        return failures == 0 ? success : fail
    }
)

const testTypedArrayLastIndexOf = parametrize<ObjectTypeAndExpectations<number[]>>(
    "testTypedArrayLastIndexOf<{{.item.objectType}}>",
    [
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(2),
                {{.item.create}}(3),
                {{.item.create}}(4),
                {{.item.create}}(5)
            ),
            [
                0, -1, -1, 3, 4, 4, 1, -1, 3, 2, 1, -1, -1
            ]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            {{.item.objectType}}.of(
                {{.item.create}}(1),
                {{.item.create}}(3),
                {{.item.create}}(3),
                {{.item.create}}(3),
                {{.item.create}}(5)
            ),
            [
                0, -1, -1, -1, 4, 4, -1, -1, -1, 3, -1, -1, -1
            ]
        ] as ObjectTypeAndExpectations<number[]>,
        [
            new {{.item.objectType}}(),
            [
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
            ]
        ] as ObjectTypeAndExpectations<number[]>,
    ],
    (args: ObjectTypeAndExpectations<number[]>): number => {
        const array = args[0]
        const expected = args[1]
        
        let failures = 0
        
        const lastIndexOfElement = array.lastIndexOf({{.item.create}}(1))
        failures += check(
            boolToResult(lastIndexOfElement == expected[0]),
            `[${array}].lastIndexOf(1) = ${lastIndexOfElement}`
        )
        
        const lastIndexOfNonExistent = array.lastIndexOf({{.item.create}}(42))
        failures += check(
            boolToResult(lastIndexOfNonExistent == expected[1]),
            `[${array}].lastIndexOf(42) = ${lastIndexOfNonExistent}`
        )
        
        const lastIndexOfWithUndefined = array.lastIndexOf({{.item.create}}(3), undefined)
        failures += check(
            boolToResult(lastIndexOfWithUndefined == expected[2]),
            `[${array}].lastIndexOf(3, undefined) = ${lastIndexOfWithUndefined}`
        )
        
        const lastIndexOfFromIndex = array.lastIndexOf({{.item.create}}(4), 3)
        failures += check(
            boolToResult(lastIndexOfFromIndex == expected[3]),
            `[${array}].lastIndexOf(4, 3) = ${lastIndexOfFromIndex}`
        )
        
        const lastIndexOfIntFromIndex = array.lastIndexOf({{.item.create}}(5), 4 as int)
        failures += check(
            boolToResult(lastIndexOfIntFromIndex == expected[4]),
            `[${array}].lastIndexOf(5, 4 as int) = ${lastIndexOfIntFromIndex}`
        )
        
        const lastIndexOfIntOutOfBounds = array.lastIndexOf({{.item.create}}(5), 10 as int)
        failures += check(
            boolToResult(lastIndexOfIntOutOfBounds == expected[5]),
            `[${array}].lastIndexOf(5, 10 as int) = ${lastIndexOfIntOutOfBounds}`
        )
        
        const lastIndexOfIntElement = array.lastIndexOf(2 as int)
        failures += check(
            boolToResult(lastIndexOfIntElement == expected[6]),
            `[${array}].lastIndexOf(2 as int) = ${lastIndexOfIntElement}`
        )
        
        const lastIndexOfIntNonExistent = array.lastIndexOf(99 as int)
        failures += check(
            boolToResult(lastIndexOfIntNonExistent == expected[7]),
            `[${array}].lastIndexOf(99 as int) = ${lastIndexOfIntNonExistent}`
        )
        
        const lastIndexOfIntBothInt = array.lastIndexOf(4 as int, 3 as int)
        failures += check(
            boolToResult(lastIndexOfIntBothInt == expected[8]),
            `[${array}].lastIndexOf(4 as int, 3 as int) = ${lastIndexOfIntBothInt}`
        )
        
        const lastIndexOfIntNegativeFromIndex = array.lastIndexOf(3 as int, -1 as int)
        failures += check(
            boolToResult(lastIndexOfIntNegativeFromIndex == expected[9]),
            `[${array}].lastIndexOf(3 as int, -1 as int) = ${lastIndexOfIntNegativeFromIndex}`
        )
        
        const lastIndexOfNegativeFromIndex = array.lastIndexOf({{.item.create}}(2), -2)
        failures += check(
            boolToResult(lastIndexOfNegativeFromIndex == expected[10]),
            `[${array}].lastIndexOf(2, -2) = ${lastIndexOfNegativeFromIndex}`
        )
        
        const lastIndexOfNegativeStartOutOfBounds = array.lastIndexOf({{.item.create}}(1), -10)
        failures += check(
            boolToResult(lastIndexOfNegativeStartOutOfBounds == expected[11]),
            `[${array}].lastIndexOf(1, -10) = ${lastIndexOfNegativeStartOutOfBounds}`
        )
        
        const lastIndexOfNonExistentWithValidStart = array.lastIndexOf(99 as int, 2 as int)
        failures += check(
            boolToResult(lastIndexOfNonExistentWithValidStart == expected[12]),
            `[${array}].lastIndexOf(99 as int, 2 as int) = ${lastIndexOfNonExistentWithValidStart}`
        )
        
        return failures == 0 ? success : fail
    }
)

function testEntries(count: number): number {
    let arr = new {{.item.objectType}}(count);
    for (let i = 0; i < count; i++) {
        arr[i] = (i + 1) as {{.item.primitiveType}};
    }
    let fails: number = 0;
    let current: number = 0;
    for (const entry of arr.entries()) {
        fails += entry[0] != current ? 1 : 0;
        fails += (entry[1]) != {{.item.create}}(current + 1) ? 1 : 0;
        current++;
    }
    fails += count != current as number ? 1 : 0;
    return fails;
}
